/*****************************************************************************/
/*                                                                           */
/*    Domino Operation System System Specific Module                         */
/*                                                                           */
/*    Copyright (C) 2010 Laszlo Arvai                                        */
/*                                                                           */
/*    ------------------------------------------------------------------     */
/*    netPPP - Network PPP implementation                                    */
/*****************************************************************************/

//! \defgroup ethernet Ethernet PPP

/*****************************************************************************/
/* Includes                                                                  */
/*****************************************************************************/
#include <krnlSystemTimer.h>
#include <netPPP.h>
#include <drvUART.h>
#include <netPacketBuffer.h>
#include <netIP.h>
#include <netDNS.h>
#include <sysCompiler.h>
#include "sysSettings.h"

/*****************************************************************************/
/* Constants                                                                 */
/*****************************************************************************/

// PPP status codes
#define netPPPSC_FRAME_START	1
#define netPPPSC_ADDRESS			2
#define netPPPSC_CONTROL			3
#define netPPPSC_PROTOCOL_1		4
#define netPPPSC_PROTOCOL_2		5
#define netPPPSC_PAYLOAD			6
#define netPPPSC_CHECKSUM_1		7
#define netPPPSC_CHECKSUM_2		8
#define netPPPSC_SKIP_PACKET	9
#define netPPPSC_FRAME_END		10

// special bytes
#define netPPP_ESC   0x7D 
#define netPPP_END   0x7E 

#define netPPP_MINCHAR    0x20    // Minimum non-escaped char
#define netPPP_XORCHAR    0x20    // Escape XOR modifier

// protocol types
#define netPPPPT_LCP  0xC021 // Link control protocol (LCP)
#define netPPPPT_PAP  0xC023 // Password authentication protocol (PAP)
#define netPPPPT_CHAP 0xC223 // Challenge handshake authentication protocol (CHAP)
#define netPPPPT_IPCP 0x8021 // Internet protocol control protocol (IPCP)
#define netPPPPT_IP   0x0021 // Internet protocol

// PPP packet code (used e.g. in LCP packets)
#define netPPPCD_REQ			1 
#define netPPPCD_ACK			2 
#define netPPPCD_NAK			3 
#define netPPPCD_REJ			4 
#define netPPPCD_TERM			5 
#define netPPPCD_TERM_ACK 6 
#define netPPPCD_CODE_REJ	7
#define netPPPCD_PROT_REJ	8
#define netPPPCD_ECHO_REQ	9
#define netPPPCD_ECHO_REP	10

// LCP option types
#define netLCPOT_AsyncControlCharacterMap						2
#define netLCPOT_AuthenticationProtocol							3
#define netLCPOT_ProtocolFieldCompression						7
#define netLCPOT_AddressAndDControlFieldCompression 8

// IPCP Option types
#define netIPCPOT_IPAddress						3
#define netIPCPOT_PrimaryDNSServer		129
#define netIPCPOT_PrimaryNBNSServer		130
#define netIPCPOT_SecondaryDNSServer	131
#define netIPCPOT_SecondaryNBNSServer	132

// Frame checksum constants
#define netPPP_INITFCS16    0xffff  // Initial FCS value
#define netPPP_GOODFCS16    0xf0b8  // Good final FCS value

// other constants
#define netMODEM_COMMAND_BUFFER_LENGTH 16
#define netPPP_MAX_SIZE 1500
#define netPPP_CONNECT_STRING_SEND_TIME 50
#define netPPP_NEGOTIATION_TIMEOUT 1000
#define netPPP_MAX_MODEM_CONNECTION_RETRY 4

/*****************************************************************************/
/* Types                                                                     */
/*****************************************************************************/ 
typedef union _netPPPLCPOptions
{
	dosByte All;
	struct
	{
		dosBool ProtocolFieldCompressionEnabled:1;
		dosBool AddressAndControlFieldCompressionEnabled:1;
		dosBool AsyncControlCharactersNotEscaped:1;
		dosBool PasswordAuthenticationProtocolEnabled:1;
	};
} netPPPLCPOptions;

typedef union _netPPPIPCPOptions
{
	dosByte All;
	struct
	{
		dosBool IPAddress:1;
		dosBool PrimaryDNSAddress:1;
		dosBool PrimaryNBNSAddress:1;
		dosBool SecondaryDNSAddress:1;
		dosBool SecondaryNBNSAddress:1;
	};
}	netPPPIPCPOptions;

typedef union _netPPPNegotiationStatus
{
	dosByte All;
	struct
	{
		dosBool OurRequestNegotiated:1;
		dosBool PeerRequestNegotiated:1;
		dosByte RetryCount:3;
	};
} netPPPNegotiationStatus;

//! PPP connection status codes
typedef enum _netPPPConnectionStatus
{
	netPPPCS_Idle,
	netPPPCS_NoConnection,
	netPPPCS_ModemConnecting,
	netPPPCS_LCPInit,
	netPPPCS_LCPInProgress,
	netPPPCS_LCPFinalizing,
	netPPPCS_IPCPInit,
	netPPPCS_IPCPInProgress,
	netPPPCS_IPCPFinalizing,
	netPPPCS_Connected,
	netPPPCS_Disconnect,
	netPPPCS_DisconnectionInProgress
} netPPPConnectionStatus;

char* l_connection_status_dbgmsg[] = 
{
	"Idle",
	"NoConnection",
	"ModemConnecting",
	"LCPInit",
	"LCPInProgress",
	"LCPFinalizing",
	"IPCPInit",
	"IPCPInProgress",
	"IPCPFinalizing",
	"Connected",
	"Disconnect",
	"DisconnectionInProgress"
};

/*****************************************************************************/
/* Module local functions                                                    */
/*****************************************************************************/ 
static void netPPPAddToFCS( dosWord* inout_fcs, dosByte in_data );
static dosWord netPacketTypeTOPPPPacketType( netPacketType in_packet_type );
static dosBool netProcessLCPOptions(dosByte in_processing_mode, dosWord *inout_length);
static dosBool netProcessIPCPOptions(dosByte in_processing_mode, dosWord *inout_length,dosBool in_peer);
static void netPPPSendLCPPacket(netPPPLCPOptions in_reuest);
static void netPPPSendIPCPPacket(netPPPIPCPOptions in_request);

/*****************************************************************************/
/* Global variables                                                          */
/*****************************************************************************/ 
IN_ADDR g_netPeerIPAddress;

/*****************************************************************************/
/* Module global variables                                                   */
/*****************************************************************************/ 

// PPP variables
static netPPPLCPOptions l_ppp_lcp_options = {0};
static netPPPIPCPOptions l_ppp_ipcp_options = {0};
static netPPPConnectionStatus l_ppp_link_status = netPPPCS_Idle;
static netPPPNegotiationStatus l_ppp_negotiation_status = {0};

// PPP client variables
#ifdef netPPP_CLIENT_MODE
static dosWord l_ppp_client_timestamp;
#endif

// LCP variables
static dosByte l_id;

// PPP RX variables
static dosByte l_rx_status = netPPPSC_FRAME_START;  // status byte of the PPP RX module
static dosWord l_rx_frame_size;
static dosBool l_esc_received = dosFalse;
static dosWord l_rx_protocol;
static dosWord l_rx_fcs;

// PPP TX variables
static dosByte l_tx_status = netPPPSC_FRAME_START;  // status byte of the PPP RX module
static dosByte l_tx_pending_char;
static dosByte l_tx_pending_char_valid;
static dosByte l_tx_esc_transmitted = dosFalse;
static dosWord l_tx_protocol;
static dosWord l_tx_fcs;

// modem command buffer
static dosConstString l_modem_command_to_send = DOS_NULL;
static dosByte l_modem_tx_pointer = 0;

static dosConstString l_modem_expected_response = DOS_NULL;
static dosByte l_modem_rx_pointer = 0;
static dosWord l_modem_response_timestamp = 0;
static netPPPModemResponseStatus l_modem_response_status = netPPPMRS_Idle;

// connection command string
static dosConstString l_connect_request = "CLIENT";
static dosConstString l_connect_response = "CLIENTSERVER";

// crc calculation table
static const dosWord l_fcs_table[16] = {
   0x0000, 0x1081, 0x2102, 0x3183, 0x4204, 0x5285, 0x6306, 0x7387,
   0x8408, 0x9489, 0xA50A, 0xB58B, 0xC60C, 0xD68D, 0xE70E, 0xF78F
};

/*****************************************************************************/
/* Functions                                                                 */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
//! Inicializes SLIP
//! This function must be called before using any SLIP function.
void netMACInitialize(void)
{   
  // init
  l_rx_status = netPPPSC_FRAME_START;
  l_esc_received = dosFalse;

  // clear buffers
	l_modem_command_to_send = DOS_NULL;
	l_modem_tx_pointer = 0;

	l_modem_expected_response = DOS_NULL;
	l_modem_rx_pointer = 0;
	l_modem_response_status = netPPPMRS_Idle;

  // init other modules
  drvPPPUARTInit();
}

///////////////////////////////////////////////////////////////////////////////
//! Starts packet transmission
void netMACStartTransmission(void)
{
	drvPPPUARTEnableTransmitInterrupt();
}

///////////////////////////////////////////////////////////////////////////////
//! Returns link status
//! \retval dosTrue if physical link is active
dosBool netMACLinkIsActive(void)
{
	return (l_ppp_link_status == netPPPCS_Connected);
}

///////////////////////////////////////////////////////////////////////////////
//! Establish connection
void netMACLinkConnect(void)
{
	l_ppp_link_status = netPPPCS_NoConnection;
}

///////////////////////////////////////////////////////////////////////////////
//! Terminate	current connection
void netMACLinkDisconnect(void)
{
	l_ppp_link_status = netPPPCS_Disconnect;
}

///////////////////////////////////////////////////////////////////////////////
//! Gets MAC address
//! \param Address of variable receiving MAC address
void netMACGetMACAddress( MAC_ADDR* out_mac_address )
{
	dosByte i;

	// no MAC address set it to zero
	for( i = 0; i < 6; i++ )
		out_mac_address->v[i] = 0;
}

/*****************************************************************************/
/* Internal functions                                                        */
/*****************************************************************************/

#pragma region UART Functions
///////////////////////////////////////////////////////////////////////////////
// UART Transmit complete interrupt
void drvPPPUARTTransmitInterrupt(void)
{
	dosByte curr_byte;
	
	// check if modem command is pending
	if( l_modem_command_to_send != DOS_NULL )
	{
		// check if data is still pending
		if( l_modem_command_to_send[l_modem_tx_pointer] != '\0'  )
		{
			// send character
			drvPPPUARTSendCharacter( l_modem_command_to_send[l_modem_tx_pointer++] );
		}
		else
		{
			// clear length and disable interrupt if there is no more character to send
			l_modem_command_to_send = DOS_NULL;
			l_modem_tx_pointer = 0;
			drvPPPUARTDisableTransmitInterrupt();
		}
	}
	else
	{
    // check if ESC were transmitted
    if( l_tx_esc_transmitted )
    {
      drvPPPUARTSendCharacter( l_tx_pending_char );
      l_tx_esc_transmitted = dosFalse;
      return;
    }

		switch( l_tx_status )
		{
			// start of frame
			case netPPPSC_FRAME_START:
				// check if packet is pending
				if( netIsTXPacketAvailable() && netIsTXPacketStart() )
				{
					// packet start flag
					drvPPPUARTSendCharacter( netPPP_END );
					
					// init FCS
					l_tx_fcs = netPPP_INITFCS16;
          l_tx_esc_transmitted = dosFalse;
					
					// init protocol
					l_tx_protocol = netPacketTypeTOPPPPacketType( netGetTXPacketType() );
  				
  				// advance status (and handle Address and control field compression)
  				if( l_ppp_lcp_options.AddressAndControlFieldCompressionEnabled )
  				{
  					l_tx_status = netPPPSC_PROTOCOL_1;
  				}
  				else
  				{
  					l_tx_status = netPPPSC_ADDRESS;
  				}
				}
				else
				{
					// no packet available
					drvPPPUARTDisableTransmitInterrupt();				
				}
				return;
				
			case netPPPSC_ADDRESS:
				// address
				curr_byte = 0xff;
				break;

			case netPPPSC_CONTROL:
				// control
				curr_byte = 0x03;
				break;
				
			case netPPPSC_PROTOCOL_1:
				// control
				if( l_ppp_lcp_options.ProtocolFieldCompressionEnabled && l_tx_protocol < 0x00ff )
				{
					curr_byte = (dosByte)l_tx_protocol;
					l_tx_status++; // skip protocol 2 sending
				}
				else
				{
					curr_byte = HIGH(l_tx_protocol);
				}
				break;
				
			case netPPPSC_PROTOCOL_2:
				// control
				curr_byte = LOW(l_tx_protocol);
				break;
				
			case netPPPSC_PAYLOAD:
				// load next byte
				if( !netReadByteTXPacket( &curr_byte ) )
				{
					// no more character available, send checkum
					l_tx_fcs = ~l_tx_fcs;					
					drvPPPUARTSendCharacter( LOW(l_tx_fcs) );
					l_tx_status = netPPPSC_CHECKSUM_2;
					return;
				}
				break;
			
			case netPPPSC_CHECKSUM_2:
				drvPPPUARTSendCharacter( HIGH(l_tx_fcs) );
				l_tx_status = netPPPSC_FRAME_END;
				return;
				
			case netPPPSC_FRAME_END:
				drvPPPUARTSendCharacter( netPPP_END );
				l_tx_status = netPPPSC_FRAME_START;
				return;
		}

    // add to CRC			
		netPPPAddToFCS( &l_tx_fcs, curr_byte );
			 
		// handle escape bytes
    if( curr_byte == netPPP_ESC || curr_byte == netPPP_END ||
				( curr_byte < netPPP_MINCHAR && !l_ppp_lcp_options.AsyncControlCharactersNotEscaped ) )
	  {
		  l_tx_pending_char = curr_byte ^ netPPP_XORCHAR;
		  l_tx_esc_transmitted = dosTrue;
      curr_byte = netPPP_ESC;
	  }
		
		// send character
		drvPPPUARTSendCharacter( curr_byte );
			
		// advance status
    if( l_tx_status != netPPPSC_PAYLOAD )
		  l_tx_status++;
	}
}

///////////////////////////////////////////////////////////////////////////////
//! Character received interrupt callback
//! Usually this function is called from the driver RX interrupt handler.
//! \param Received character
void drvPPPUARTReceiveInterrupt( dosChar in_char )
{
	// check for packet start
  if( l_rx_status == netPPPSC_FRAME_START )
  {
    // check for packet start
    if( in_char == netPPP_END )
    {
      // start reception
      l_rx_status++;
			l_rx_fcs = netPPP_INITFCS16;	// init fcs
    }
    else
    {
			// check if modem response is expected
			if( l_modem_expected_response != DOS_NULL ) 
			{
				if( l_modem_expected_response[l_modem_rx_pointer] != in_char )
				{
					// invalid response
					l_modem_response_status = netPPPMRS_Invalid;
					l_modem_expected_response = DOS_NULL;
				}
				else
					l_modem_rx_pointer++; // next character

				// check is response is received
				if( l_modem_expected_response != DOS_NULL && l_modem_expected_response[l_modem_rx_pointer] == '\0' )
				{
					// response received
					l_modem_response_status = netPPPMRS_Received;
					l_modem_expected_response = DOS_NULL;
				}
			}
    }
  }
  else
  {
    // check for ESC
    if( l_esc_received )
    {
      // remove flag in_char contains next character
      l_esc_received = dosFalse;

      in_char ^= 0x20;
    }
    else
    {
			// handle special characters			
      switch( in_char )
      {
        // check for ESC
        case netPPP_ESC:
          l_esc_received = dosTrue;
          break;

        // check packet end
        case netPPP_END:
          // store packet if valid or delete packet
          if( l_rx_status == netPPPSC_PAYLOAD  && l_rx_frame_size > 2 && l_rx_fcs == netPPP_GOODFCS16 )
          { 
						// store packet but without FCS
            netStoreAndSetLengthRXPacket(l_rx_frame_size - 2);
          }
          else
            netCancelRXPacket();

					// waiting for the next packet
          l_rx_status = netPPPSC_FRAME_START;
          break;
          
				// drop unescaped control character
        default:
					if( !l_ppp_lcp_options.AsyncControlCharactersNotEscaped && in_char < 0x20 )
						return;
					break;
      }
    }

    // process character
    if( !l_esc_received )
    {
			// update checksum
			netPPPAddToFCS( &l_rx_fcs, in_char );
    
      // check status
      switch( l_rx_status )
      {
        // control byte (should be 0x03, ignored)
        case netPPPSC_CONTROL:
          l_rx_status++;
          break;
          
        // adress byte (should be 0xff)
				case netPPPSC_ADDRESS:
					// if address and control is enabled and the received byte if not FF
					if( !l_ppp_lcp_options.AddressAndControlFieldCompressionEnabled || in_char == 0xff )
					{
						l_rx_status++;
						break;
					}
					
					// no break!!!

        // protocol (first byte)
        case netPPPSC_PROTOCOL_1:
					// handle protocol field compression
					if( !l_ppp_lcp_options.ProtocolFieldCompressionEnabled || (in_char & 1) == 0 )
					{
						l_rx_protocol = in_char << 8;
						l_rx_status = netPPPSC_PROTOCOL_2;
						break;
					}
					else
					{
						l_rx_protocol = 0;
					}
					
					// no break!!! 

        // protocol (second byte)
        case netPPPSC_PROTOCOL_2:
        {
          dosByte protocol = netPT_UNKNOWN;

          l_rx_protocol += in_char;
          l_rx_status = netPPPSC_PAYLOAD;

          // check protocol
          switch( l_rx_protocol )
          {
            // Link control protocol (LCP)
            case netPPPPT_LCP:
              protocol = netPT_LCP;
              break;

            // Password authentication protocol (PAP)
            case netPPPPT_PAP:
              protocol = netPT_PAP;
              break;

            // Challenge handshake authentication protocol (CHAP)
            case netPPPPT_CHAP:
              protocol = netPT_CHAP;
              break;

            // Internet protocol control protocol (IPCP)
            case netPPPPT_IPCP:
              protocol = netPT_IPCP;
              break;
            
            // Internet protocol
            case netPPPPT_IP:
              protocol = netPT_IP;
              break;

						// unknown protocol
            default:
							protocol = netPT_UknownPPP;
							// if the connection is already established silently skip this packet
							if( l_ppp_link_status == netPPPCS_Connected )
							{
								l_rx_status = netPPPSC_SKIP_PACKET;
							}
							break;
          }

					// create RX packet
          l_rx_frame_size = 0;
          netCreateRXPacket(protocol);
          
          // store original protocol if it's unknown
          if( protocol == netPT_UknownPPP )
          {
						// store the original packet type at the beginning
						netWriteByteRXPacket( HIGH(l_rx_protocol) );
						netWriteByteRXPacket( LOW(l_rx_protocol) );
						l_rx_frame_size += 2;
					}
				}
        break;

        // payload
        case netPPPSC_PAYLOAD:
          if( netWriteByteRXPacket( in_char ) )
          {
            l_rx_frame_size++;

            // Avoid & discard large PPP packets
            if( l_rx_frame_size > netPPP_MAX_SIZE )
            {
              netCancelRXPacket();
              l_rx_status = netPPPSC_SKIP_PACKET;
            }
          }
          else
          {
            // skip packet beacause there is no buffer
            l_rx_status = netPPPSC_SKIP_PACKET;
          }
          break;
      }
    }
  }
}

#pragma endregion

#pragma region LCP Functions
///////////////////////////////////////////////////////////////////////////////
//! State Machine that implements LCP packet negotiation   
void netLCPProcess(void)
{   
  dosByte code;
  dosWord length;
  dosByte id;
  dosWord pos;
  dosWord options_length;
  dosWord length_pos;
  dosByte options_mode;
  dosBool exit;

	// check if packet really available and check type
  if( netGetAvailablePacketType() != netPT_LCP )
		return;

  // drop invalid packet length
  if( netGetRXPacketLength() < 4 )
  {
  	netDeleteRXPacket();
    return;
  }

  // get buffer and unlink packet and prepare as TX packet
	netOpenRXPacket();

  // cache important fields
  netReadByteRXPacket( &code );
  netReadByteRXPacket( &id );
  netReadWordRXPacket( &length );

  // decide LPT type
  switch( code )
  {
    // Configure-Request
    case netPPPCD_REQ:
			// store current position
			pos = netGetRXPacketPos();
			
			// process NAK options
			options_mode = netPPPCD_REJ;
			exit = dosFalse;
			while( !exit )
			{
				// reset RX packet positon to LCP options
				netSetRXPacketPos(pos);

				// create LCP response packet					
				netCreateTXPacket(netPT_LCP);
				netWriteByteTXPacket(options_mode);
				netWriteByteTXPacket(id);
				length_pos = netGetTXPacketPos();
				netWriteWordTXPacket(0);						// update length later

				// process options
				options_length = 0;
				while( netProcessLCPOptions( options_mode, &options_length) );
			
				// send packet if there are options to send
				if( options_length != 0 || options_mode == netPPPCD_ACK )
				{
					// update length
					netSetTXPacketPos(length_pos);
					netWriteWordTXPacket(options_length + 4); // add header length
          netSendTXPacket();
					
					exit = dosTrue;          
        }
        else
					netCancelTXPacket();
				
				// next mode
				switch( options_mode )
				{
					case netPPPCD_REJ:
						options_mode = netPPPCD_NAK;
						break;
						
					case netPPPCD_NAK:
						options_mode = netPPPCD_ACK;
						break;
						
					case netPPPCD_ACK:
						l_ppp_negotiation_status.PeerRequestNegotiated = dosTrue;
						exit = dosTrue;
						break;
						
					default:
						exit = dosTrue;
						break;
				}
			}
			break;   
   
    // Server Acknowledged our LCP settings
    case netPPPCD_ACK:   
			l_ppp_negotiation_status.OurRequestNegotiated = dosTrue;
      break;   
   
    // NAK
    case netPPPCD_NAK:   
      break;   
   
    // REJ
    case netPPPCD_REJ:   
      break;   
           
    // Terminate ACK
    case netPPPCD_TERM_ACK:
#ifdef netPPP_CLIENT_AUTO_CONNECT
      l_ppp_link_status = netPPPCS_NoConnection;   
#else						
			l_ppp_link_status = netPPPCS_Idle;
#endif
      break;   

    // Server Terminate-Request received
    case netPPPCD_TERM:
			// create LCP header
			netCreateTXPacket(netPT_LCP);
			netWriteByteTXPacket(netPPPCD_TERM_ACK);
			netWriteByteTXPacket(l_id);
			netWriteWordTXPacket(4);						// zero length
    
			// send packet
			netSendTXPacket();
			l_ppp_lcp_options.All = 0;   

#ifdef netPPP_CLIENT_AUTO_CONNECT
      l_ppp_link_status = netPPPCS_NoConnection;   
#else						
			l_ppp_link_status = netPPPCS_Idle;
#endif
			
      break;
  }

  // cancel packet
  netDeleteRXPacket();
}  

///////////////////////////////////////////////////////////////////////////////
//! Processes one LPC option and copies it to the TX buffer if needed
//! \param Select process mode (REJ, NAK, ACK)
//! \param Length of already processed options (in byte)
//! \retval No more option exist in RX packet
static dosBool netProcessLCPOptions(dosByte in_processing_mode, dosWord *inout_length)
{
	dosByte option_type;
	dosByte option_length;
	dosByte copy;
	dosByte buffer;
	dosWord pos;
	
	// init	
	copy = dosFalse;
	
	// read option header
	if( !netReadByteRXPacket(&option_type) )
		return dosFalse;
		
	if( !netReadByteRXPacket(&option_length) )
		return dosFalse;
		
	pos = netGetRXPacketPos();
	
	switch( option_type )
	{
		// Async control character map
		case netLCPOT_AsyncControlCharacterMap:
		{
			dosDWord map;

			// read map			
			if( !netReadDWordRXPacket(&map) )
				return dosFalse;
				
			switch( in_processing_mode )
			{
				case netPPPCD_REJ:
					// reject if not all characters are off
					if( map != 0 )
						copy = dosTrue;
					break;
					
				case netPPPCD_ACK:
					// ack if all characters are off 
					if( map == 0 )
					{
						l_ppp_lcp_options.AsyncControlCharactersNotEscaped = dosTrue;
						copy = dosTrue;
					}
					break;
			}
		}
		break;

		// authentication protovol
		case netLCPOT_AuthenticationProtocol:
		{
			dosWord protocol;

			// read map			
			if( !netReadWordRXPacket(&protocol) )
				return dosFalse;
				
			switch( in_processing_mode )
			{
				case netPPPCD_REJ:
					// reject if not PAP
					if( protocol != netPPPPT_PAP )
						copy = dosTrue;
					break;
					
				case netPPPCD_ACK:
					// ack if PAP
					if( protocol == netPPPPT_PAP )
					{
						l_ppp_lcp_options.PasswordAuthenticationProtocolEnabled = dosTrue;
						copy = dosTrue;
					}
					break;
			}
		}
		break;

		// Protocol compression
		case netLCPOT_ProtocolFieldCompression:
			if( in_processing_mode == netPPPCD_ACK )
			{
				copy = dosTrue;
				l_ppp_lcp_options.AddressAndControlFieldCompressionEnabled = dosTrue;
			}
			break;
			
		// Address and Control Field Compression
		case netLCPOT_AddressAndDControlFieldCompression:
			if( in_processing_mode == netPPPCD_ACK )
			{
				copy = dosTrue;
				l_ppp_lcp_options.ProtocolFieldCompressionEnabled = dosTrue;
			}
			break;
			
		default:
			// if we are in reject mode -> reject all unknown option
			if(in_processing_mode == netPPPCD_REJ)
				copy = dosTrue;
			break;
	}
	
	// restore position
	netSetRXPacketPos(pos);
	
	// if copy requested write header
	if( copy )
	{
		netWriteByteTXPacket(option_type);
		netWriteByteTXPacket(option_length);
		*inout_length += option_length;
	}

	// copy option to TX buffer	
	option_length -= 2; // remove header length
	while( option_length > 0 )
	{
		// read from RX packet
		netReadByteRXPacket( &buffer );
		
		// copy only when it's requested
		if( copy )
			netWriteByteTXPacket( buffer );
			
		option_length--;
	}
	
	return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Starts LCP negotiation procedure
static void netPPPSendLCPPacket(netPPPLCPOptions in_request)
{
	dosByte length;
	dosWord options_length;
  dosWord length_pos;

	options_length = 4; // LCP header length
	
	// create LCP header
	netCreateTXPacket(netPT_LCP);
	netWriteByteTXPacket(netPPPCD_REQ);
	netWriteByteTXPacket(l_id);
	length_pos = netGetTXPacketPos();
	netWriteWordTXPacket(0);						// update length later

	// async control character map
	if( in_request.AsyncControlCharactersNotEscaped )
	{
		length = 6;
		netWriteByteTXPacket(netLCPOT_AsyncControlCharacterMap);
		netWriteByteTXPacket(length);
		netWriteDWordTXPacket(0);
		options_length += length;
	}

	// Protocol field compression
	if( in_request.ProtocolFieldCompressionEnabled )
	{
		length = 2;
		netWriteByteTXPacket(netLCPOT_ProtocolFieldCompression);
		netWriteByteTXPacket(length);
		options_length += length;
	}

	// Address and control field compression
	if( in_request.AddressAndControlFieldCompressionEnabled )
	{
		length = 2;
		netWriteByteTXPacket(netLCPOT_AddressAndDControlFieldCompression);
		netWriteByteTXPacket(length);
		options_length += length;
	}

	// Password authentication protocol
	if( in_request.PasswordAuthenticationProtocolEnabled )
	{
		length = 4;
		netWriteByteTXPacket(netLCPOT_AuthenticationProtocol);
		netWriteByteTXPacket(length);
		netWriteWordTXPacket(netPPPPT_PAP);
		options_length += length;
	}

	// Update length
	netSetTXPacketPos(length_pos);
	netWriteWordTXPacket(options_length);

	// send packet
	netSendTXPacket();
	l_id++;
}
#pragma endregion

#pragma region IPCP Functions
///////////////////////////////////////////////////////////////////////////////
//! State Machine that implements IPCP packet negotiation   
void netIPCPProcess(void)
{   
  dosByte code;
  dosWord length;
  dosByte id;
  dosWord pos;
  dosWord options_length;
  dosWord length_pos;
  dosByte options_mode;
  dosBool exit;

	// check if packet really available and check type
	if( netGetAvailablePacketType() != netPT_IPCP )
		return;

  // drop invalid packet length
  if( netGetRXPacketLength() < 4 )
  {
  	netDeleteRXPacket();
    return;
  }

  // get buffer and unlink packet and prepare as TX packet
	netOpenRXPacket();

  // cache important fields
  netReadByteRXPacket( &code );
  netReadByteRXPacket( &id );
  netReadWordRXPacket( &length );

  // decide IPCP type
  switch( code )
  {
    // Configure-Request
    case netPPPCD_REQ:
			pos = netGetRXPacketPos();
			
			// process NAK options
			options_mode = netPPPCD_REJ;
			exit = dosFalse;
			while( !exit )
			{
				// reset RX packet positon to IPCP options
				netSetRXPacketPos(pos);

				// create IPCP response packet
				netCreateTXPacket(netPT_IPCP);
				netWriteByteTXPacket(options_mode);
				netWriteByteTXPacket(id);
				length_pos = netGetTXPacketPos();
				netWriteWordTXPacket(0);						// update length later

				// process options
				options_length = 0;
				while( netProcessIPCPOptions( options_mode, &options_length, dosFalse) );
			
				// send packet if there are options to send
				if( options_length != 0 || options_mode == netPPPCD_ACK )
				{
					// update length
					netSetTXPacketPos(length_pos);
					netWriteWordTXPacket(options_length + 4); // add header length
          netSendTXPacket();
          exit = dosTrue;
         }
        else
					netCancelTXPacket();
				
				// next mode
				switch( options_mode )
				{
					case netPPPCD_REJ:
						options_mode = netPPPCD_NAK;
						break;
						
					case netPPPCD_NAK:
						options_mode = netPPPCD_ACK;
						break;
						
					case netPPPCD_ACK:
						l_ppp_negotiation_status.PeerRequestNegotiated = dosTrue;
						exit = dosTrue;
						break;
						
					default:
						exit = dosTrue;
						break;
				}
			}
			break;   
   
    // ACK
    case netPPPCD_ACK:   
			l_ppp_negotiation_status.OurRequestNegotiated = dosTrue;
      break;   
   
    // NAK and REJ
    case netPPPCD_NAK:   
    case netPPPCD_REJ:   
			// some options were rejected, remove them from the request
			options_length = 0;
			while( netProcessIPCPOptions( code, &options_length, dosTrue) );

			// send request again
			netPPPSendIPCPPacket(l_ppp_ipcp_options);
      break;   
  }

  // cancel packet
  netDeleteRXPacket();
}  

///////////////////////////////////////////////////////////////////////////////
//! Processes one IPCP option and copies it to the TX buffer if needed
//! \param Select process mode (REJ, NAK, ACK)
//! \param Length of already processed options (in byte)
//! \param True in the processed message comes from the peer (not ours)
//! \retval No more option exist in RX packet
static dosBool netProcessIPCPOptions(dosByte in_processing_mode, dosWord *inout_length, dosBool in_peer)
{
	dosByte option_type;
	dosByte option_length;
	dosByte copy;
  dosByte buffer;
  dosWord pos;
  dosDWord ip;
	
	// init	
	copy = dosFalse;
	
	// read option header
	if( !netReadByteRXPacket(&option_type) )
		return dosFalse;
		
	if( !netReadByteRXPacket(&option_length) )
		return dosFalse;
	
	switch( option_type )
	{
    // IP address
    case netIPCPOT_IPAddress:
      // store data pos
      pos = netGetRXPacketPos();

      netReadDWordRXPacket(&ip);

      switch(in_processing_mode)
      {
        case netPPPCD_REJ:
					if( in_peer )
					{
						l_ppp_ipcp_options.IPAddress = dosFalse;
					}
					else
					{
						if( ip == 0 )
							copy = dosTrue;
					}
          break;

        case netPPPCD_ACK:
          if( ip != 0 )
          {
            copy = dosTrue;
            g_netPeerIPAddress.Val = htonl(ip);
          }
          break;

				case netPPPCD_NAK:
					if( in_peer )
					{
						g_netIPAddress.Val = htonl(ip);
					}
					break;
      }

      // restore packet position
      netSetRXPacketPos(pos);

      break;

    // primary DNS server
    case netIPCPOT_PrimaryDNSServer:
      // store data pos
      pos = netGetRXPacketPos();

      netReadDWordRXPacket(&ip);

      switch(in_processing_mode)
      {
        case netPPPCD_REJ:
					if( in_peer )
					{
						l_ppp_ipcp_options.PrimaryDNSAddress = dosFalse;
					}
					else
					{
						if( ip == 0 )
							copy = dosTrue;
					}
          break;

        case netPPPCD_ACK:
          if( ip != 0 )
          {
            copy = dosTrue;
            g_netPrimaryDNSAddress.Val = htonl(ip);
          }
          break;

        case netPPPCD_NAK:
					if( in_peer )
					{
						g_netPrimaryDNSAddress.Val = htonl(ip);
					}
          break;
      }

      // restore packet position
      netSetRXPacketPos(pos);

      break;

    // primary NBNS server
    case netIPCPOT_PrimaryNBNSServer:
      // store data pos
      pos = netGetRXPacketPos();

      netReadDWordRXPacket(&ip);

      switch(in_processing_mode)
      {
        case netPPPCD_REJ:
					if( in_peer )
					{
						l_ppp_ipcp_options.PrimaryNBNSAddress = dosFalse;
					}
					else
					{
					}
          break;

        case netPPPCD_ACK:
          if( ip != 0 )
          {
            copy = dosTrue;
            g_netSecondaryDNSAddress.Val = htonl(ip);
          }
          break;
      }

      // restore packet position
      netSetRXPacketPos(pos);

      break;

		// Secondary DNS server
    case netIPCPOT_SecondaryDNSServer:
      // store data pos
      pos = netGetRXPacketPos();

      netReadDWordRXPacket(&ip);

      switch(in_processing_mode)
      {
        case netPPPCD_REJ:
					if( in_peer )
					{
						l_ppp_ipcp_options.SecondaryDNSAddress = dosFalse;
					}
					else
					{
						if( ip == 0 )
							copy = dosTrue;
					}
          break;

        case netPPPCD_ACK:
          if( ip != 0 )
          {
            copy = dosTrue;
            //g_netSecondaryDNSAddress.Val = htonl(ip);
          }
          break;

        case netPPPCD_NAK:
					if( in_peer )
					{
						g_netSecondaryDNSAddress.Val = htonl(ip);
					}
          break;
      }

      // restore packet position
      netSetRXPacketPos(pos);

      break;

		// Secondary NBNS server
    case netIPCPOT_SecondaryNBNSServer:
      // store data pos
      pos = netGetRXPacketPos();

      netReadDWordRXPacket(&ip);

      switch(in_processing_mode)
      {
        case netPPPCD_REJ:
					if( in_peer )
					{
						l_ppp_ipcp_options.SecondaryNBNSAddress = dosFalse;
					}
					else
					{
					}
          break;

        case netPPPCD_ACK:
          break;
      }

      // restore packet position
      netSetRXPacketPos(pos);

      break;

		default:
			// if we are in reject mode -> reject all unknown option
			if(in_processing_mode == netPPPCD_REJ)
				copy = dosTrue;
			break;
	}
	
	// if copy requested write header
	if( copy )
	{
		netWriteByteTXPacket(option_type);
		netWriteByteTXPacket(option_length);
		*inout_length += option_length;
	}

	// copy option to TX buffer	
	option_length -= 2; // remove header length
	while( option_length > 0 )
	{
		// read from RX packet
		netReadByteRXPacket( &buffer );
		
		// copy only when it's requested
		if( copy )
			netWriteByteTXPacket( buffer );
			
		option_length--;
	}
	
	return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Starts IPCP negotiation
void netPPPSendIPCPPacket(netPPPIPCPOptions in_request)
{
	dosByte length;
	dosWord options_length;
  dosWord length_pos;

	length = 6;
	options_length = 4; // IPCP header length

	// create IPCP header
	netCreateTXPacket(netPT_IPCP);
	netWriteByteTXPacket(netPPPCD_REQ);
	netWriteByteTXPacket(l_id);
	length_pos = netGetTXPacketPos();
	netWriteWordTXPacket(0);						// update length later

	// IP Address
	if( in_request.IPAddress )
	{
		netWriteByteTXPacket(netIPCPOT_IPAddress);
		netWriteByteTXPacket(length);
		netWriteDWordTXPacket(htonl(g_netIPAddress.Val));
		options_length += length;
	}

	// Primary DNS Address
	if( in_request.PrimaryDNSAddress )
	{
		netWriteByteTXPacket(netIPCPOT_PrimaryDNSServer);
		netWriteByteTXPacket(length);
#ifdef netDNS_ENABLED
    netWriteDWordTXPacket(htonl(g_netPrimaryDNSAddress.Val));
#else
    netWriteDWordTXPacket(0);
#endif
		options_length += length;
	}

	// Primary NBNS Address
	if( in_request.PrimaryNBNSAddress )
	{
		netWriteByteTXPacket(netIPCPOT_PrimaryNBNSServer);
		netWriteByteTXPacket(length);
		netWriteDWordTXPacket(0);
		options_length += length;
	}

	// Secondary DNS Address
	if( in_request.SecondaryDNSAddress )
	{
		netWriteByteTXPacket(netIPCPOT_SecondaryDNSServer);
		netWriteByteTXPacket(length);
		netWriteDWordTXPacket(0);
		options_length += length;
	}

	// Secondary NBNS Address
	if( in_request.SecondaryNBNSAddress )
	{
		netWriteByteTXPacket(netIPCPOT_SecondaryNBNSServer);
		netWriteByteTXPacket(length);
		netWriteDWordTXPacket(0);
		options_length += length;
	}

	// Update length
	netSetTXPacketPos(length_pos);
	netWriteWordTXPacket(options_length);

	// send packet
	netSendTXPacket();
	l_id++;
}

#pragma endregion

///////////////////////////////////////////////////////////////////////////////
//! Processing unknown PPP packet
void netPPPUnknownProcess(void)
{
  dosByte code;
  dosWord length;
  dosByte id;
  dosByte buffer;
  dosWord orig_type;
  dosByte type;
  dosWord pos;
  
	// only reply when link is not established, otherwise delete silently
	if( l_ppp_link_status != netPPPCS_Connected )
	{
		// open packet
		netOpenRXPacket();

		// get packet type and original type
		type = netGetAvailablePacketType();
		if( type == netPT_UknownPPP )
			netReadWordRXPacket( &orig_type );	
		else
			orig_type = netPacketTypeTOPPPPacketType( type );
			
		pos = netGetRXPacketPos();

		// cache important fields
		netReadByteRXPacket(&code);
		netReadByteRXPacket(&id);
		netReadWordRXPacket(&length);

		// create new packet
		netCreateTXPacket(netPT_LCP);
		netWriteByteTXPacket(netPPPCD_PROT_REJ);
		netWriteByteTXPacket(id);
		netWriteWordTXPacket(length + 6);
		netWriteWordTXPacket(orig_type);
		
		// start copy from the beginning
		netSetRXPacketPos(pos);
		while( netReadByteRXPacket( &buffer ) )
		{
      netWriteByteTXPacket( buffer );
    }
    
    // send packet
		netSendTXPacket();
	}
	
	// remove packet
	netDeleteRXPacket();
}

#pragma region Modem Functions

///////////////////////////////////////////////////////////////////////////////
//! Sends modem command
//! \param Command to send to the modem
//! \return True if command was sent successfully
dosBool netPPPSendModemCommand( dosConstString in_command )
{
	// check if no other command is pending
	if( l_modem_command_to_send != DOS_NULL )
		return dosFalse;

	// prepare for sending
	l_modem_rx_pointer = 0;
	l_modem_tx_pointer = 0;
	l_modem_command_to_send = in_command;

	// start transmission
	drvPPPUARTEnableTransmitInterrupt();

	return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Waits for modem response
//! \param Response string to wait for
void netPPPStartWaitingForModemResponse( dosConstString in_response )
{
	l_modem_rx_pointer = 0;
	l_modem_response_timestamp = krnlGetSystemTimer();
	l_modem_expected_response = in_response;
}

///////////////////////////////////////////////////////////////////////////////
//! Gets modem response status
//! 

#pragma endregion

///////////////////////////////////////////////////////////////////////////////
//! Establishes the connection to the PPP server
#ifdef netPPP_CLIENT_MODE
void netPPPProcess(void)
{
	static netPPPConnectionStatus prev_link_status = netPPPCS_Idle;

	if( prev_link_status != l_ppp_link_status )
	{
		printf("State: (%2d)- %s\n", l_ppp_link_status, l_connection_status_dbgmsg[l_ppp_link_status] );
		prev_link_status  = l_ppp_link_status;
	}

	// if connected there is nothing to do
	if( l_ppp_link_status == netPPPCS_Connected )
		return;

	// check PPP connection state machine
	switch( l_ppp_link_status )
	{
		// no connection -> start connection sequence
		case netPPPCS_NoConnection:
			l_ppp_negotiation_status.All = 0;
			l_ppp_client_timestamp = krnlGetSystemTimer();
			l_ppp_link_status = netPPPCS_ModemConnecting;
			break;

		// check response and/or send connection string
		case netPPPCS_ModemConnecting:
			// check for connection response
			if( l_modem_response_status == netPPPMRS_Received )
			{
				l_ppp_link_status = netPPPCS_LCPInit;
				break;
			}

			// send new connection request 
			if( krnlGetTimeSince(l_ppp_client_timestamp) > netPPP_CONNECT_STRING_SEND_TIME )
			{
				// update retry count
				if( l_ppp_negotiation_status.RetryCount < netPPP_MAX_MODEM_CONNECTION_RETRY )
					l_ppp_negotiation_status.RetryCount++;
				else
				{
					// no more retry, restart connection
					l_ppp_link_status = netPPPCS_NoConnection;
					break;
				}

				// prepare for answer
				netPPPStartWaitingForModemResponse( l_connect_response );

				// send connection request
				netPPPSendModemCommand(	l_connect_request );

	 			l_ppp_client_timestamp = krnlGetSystemTimer();
			}
			break;

		// initialize LCP megotiation
		case netPPPCS_LCPInit:
		{
			netPPPLCPOptions request;

			// init request structure
			request.ProtocolFieldCompressionEnabled = dosTrue;
			request.AddressAndControlFieldCompressionEnabled = dosTrue;
			request.AsyncControlCharactersNotEscaped = dosTrue;
			request.PasswordAuthenticationProtocolEnabled = dosFalse;

 			l_ppp_client_timestamp = krnlGetSystemTimer();
 			
 			// init negotiation flags
 			l_ppp_negotiation_status.All = 0;

			// start LCP negotiation
			netPPPSendLCPPacket(request);
			l_ppp_link_status = netPPPCS_LCPInProgress;
		}
		break;

		case netPPPCS_LCPInProgress:
			// check for timeout
			if( krnlGetTimeSince(l_ppp_client_timestamp) > netPPP_NEGOTIATION_TIMEOUT )
			{
				// LCP negotiation failed -> disconnect

			}
			else
			{
				// LCP negotiation was finished, start IPCP negotiation
				if( l_ppp_negotiation_status.OurRequestNegotiated && l_ppp_negotiation_status.PeerRequestNegotiated )
				{
					l_ppp_negotiation_status.All = 0;
					l_ppp_link_status = netPPPCS_IPCPInit;
				}
			}
			break;

		// Start IPCP negotiation
		case netPPPCS_IPCPInit:
			// enable all options
      l_ppp_ipcp_options.IPAddress = dosTrue;
#ifdef netDNS_ENABLED
      l_ppp_ipcp_options.PrimaryDNSAddress = dosTrue;
#else
      l_ppp_ipcp_options.PrimaryDNSAddress = dosFalse;
#endif
      l_ppp_ipcp_options.PrimaryNBNSAddress = dosTrue;
#ifdef netDNS_ENABLED
      l_ppp_ipcp_options.SecondaryDNSAddress = dosTrue;
#else
      l_ppp_ipcp_options.SecondaryDNSAddress = dosFalse;
#endif
      l_ppp_ipcp_options.SecondaryNBNSAddress = dosTrue;

 			l_ppp_client_timestamp = krnlGetSystemTimer();

			// send invalid IP to initiate IPCP negotiation
			netPPPSendIPCPPacket(l_ppp_ipcp_options);

			l_ppp_link_status = netPPPCS_IPCPInProgress;
			break;

		case netPPPCS_IPCPInProgress:
			// check for timeout
			if( krnlGetTimeSince(l_ppp_client_timestamp) > netPPP_NEGOTIATION_TIMEOUT )
			{
				// IPCP negotiation failed -> disconnect

			}
			else
			{
				// LCP negotiation was finished, start IPCP negotiation
				if( l_ppp_negotiation_status.OurRequestNegotiated && l_ppp_negotiation_status.PeerRequestNegotiated )
				{
					l_ppp_negotiation_status.All = 0;
					l_ppp_link_status = netPPPCS_Connected;
				}
			}
			break;

		// start PPP connection termination
		case netPPPCS_Disconnect:
			// create LCP header
 			netCreateTXPacket(netPT_LCP);
			netWriteByteTXPacket(netPPPCD_TERM);
			netWriteByteTXPacket(l_id);
			netWriteWordTXPacket(4);

			// send packet
			netSendTXPacket();
			l_id++;

			// change status
			l_ppp_link_status = netPPPCS_DisconnectionInProgress;
			break;

		case netPPPCS_DisconnectionInProgress:
			break;
	}
}
#endif

///////////////////////////////////////////////////////////////////////////////
//! Adds one byte to FCS
//! \param Pointer to fcs value
//! \param Byte to include in the fcs
static void netPPPAddToFCS( dosWord* inout_fcs, dosByte in_data )
{
	dosWord crc = *inout_fcs;
	
	crc ^= in_data;
	crc = (crc >> 4) ^ l_fcs_table[0xF & crc];
	crc = (crc >> 4) ^ l_fcs_table[0xF & crc];	
                
  *inout_fcs = crc;
}

///////////////////////////////////////////////////////////////////////////////
//! Converts internal packet type to PPP packet type
//! \param Internal packet type
//! \return PPP packet type
static dosWord netPacketTypeTOPPPPacketType( netPacketType in_packet_type )
{
	switch( in_packet_type )
	{
		case netPT_IP:
			return netPPPPT_IP;
			
		case netPT_LCP:
			return netPPPPT_LCP;
			
		case netPT_PAP:
			return netPPPPT_PAP;
			
		case netPT_CHAP:
			return netPPPPT_CHAP;
			
		case netPT_IPCP:
			return netPPPPT_IPCP;
	}
	
	return 0;
}